#!/usr/bin/env python3
"""
演示 exec(code, locals()) 中如何传递构建命令给 setup.py 代码
"""

import sys
import os

def demonstrate_command_passing():
    """演示命令传递的核心机制"""
    
    print("🔍 exec(code, locals()) 中的命令传递机制")
    print("=" * 60)
    
    print("""
关键问题：
- exec(code, locals()) 执行 setup.py 内容
- setup.py 需要知道执行什么命令 (build/install/egg_info)
- 命令是如何传递的？

答案：通过 sys.argv！
    """)

def show_mechanism_step_by_step():
    """逐步展示机制"""
    
    print("\n🔧 setuptools.build_meta 的命令传递机制:")
    print("-" * 50)
    
    mechanism_code = '''
# setuptools.build_meta 内部逻辑 (简化版)

def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
    """PEP 517 构建 wheel 的钩子函数"""
    
    # 1. 保存原始 sys.argv
    original_argv = sys.argv.copy()
    
    try:
        # 2. 设置构建命令的 sys.argv
        sys.argv = ['setup.py', 'bdist_wheel', '--dist-dir', wheel_directory]
        
        # 3. 读取 setup.py 内容
        with open('setup.py', 'r') as f:
            setup_code = f.read()
        
        # 4. 准备执行环境
        exec_globals = {
            '__file__': os.path.abspath('setup.py'),
            '__name__': '__main__',
            '__builtins__': __builtins__,
            # 重要：sys 模块在这里可以被访问！
        }
        
        # 5. 执行 setup.py 代码
        #    setup.py 中的代码可以通过 import sys 访问 sys.argv
        exec(setup_code, exec_globals)
        
        # 6. 收集构建结果
        return find_built_wheel(wheel_directory)
        
    finally:
        # 7. 恢复原始 sys.argv
        sys.argv = original_argv

def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
    """PEP 517 可编辑安装的钩子函数"""
    
    original_argv = sys.argv.copy()
    try:
        # 设置开发模式安装的命令
        sys.argv = ['setup.py', 'develop']
        # ... 执行 setup.py
    finally:
        sys.argv = original_argv

def get_metadata():
    """获取元数据的钩子函数"""
    
    original_argv = sys.argv.copy()
    try:
        # 设置元数据收集命令
        sys.argv = ['setup.py', 'egg_info']  # ← 这就是你看到的！
        # ... 执行 setup.py
    finally:
        sys.argv = original_argv
    '''
    
    print(mechanism_code)

def demonstrate_with_simulation():
    """用模拟演示这个过程"""
    
    print("\n🎭 模拟演示不同的构建阶段:")
    print("-" * 40)
    
    # 模拟的 setup.py 内容
    setup_py_content = '''
import sys
print(f"setup.py 收到的命令: {sys.argv}")

def setup(**kwargs):
    """模拟的 setup 函数"""
    print(f"setup() 开始执行...")
    
    # setup() 函数通过 sys.argv 判断要执行什么操作
    if len(sys.argv) > 1:
        command = sys.argv[1]
        print(f"检测到命令: {command}")
        
        if command == 'egg_info':
            print("  -> 执行元数据收集")
            print("  -> 生成 .egg-info 目录")
            print("  -> 收集包信息，不编译代码")
            
        elif command == 'build_ext':
            print("  -> 执行扩展编译")
            print("  -> 调用 CMakeBuild")
            print("  -> 编译 C/C++ 代码")
            
        elif command == 'bdist_wheel':
            print("  -> 执行 wheel 打包")
            print("  -> 创建 .whl 文件")
            
        elif command == 'develop':
            print("  -> 执行开发模式安装")
            print("  -> 创建可编辑链接")
    
    return "success"

# setup.py 的调用
result = setup(
    name="my_package",
    ext_modules=["CMakeExtension(...)"],
    cmdclass={"build_ext": "CMakeBuild"}
)
print(f"setup() 返回: {result}")
'''
    
    # 模拟不同阶段的 build_meta 调用
    stages = [
        ("元数据收集", ['setup.py', 'egg_info']),
        ("扩展编译", ['setup.py', 'build_ext', '--inplace']),
        ("wheel 打包", ['setup.py', 'bdist_wheel', '--dist-dir', './dist']),
        ("开发安装", ['setup.py', 'develop']),
    ]
    
    for stage_name, argv in stages:
        print(f"\n📋 模拟阶段: {stage_name}")
        print(f"   设置 sys.argv = {argv}")
        print("   执行 exec(setup_py_content, globals()):")
        print("   " + "-" * 45)
        
        # 保存当前 sys.argv
        original_argv = sys.argv.copy()
        
        try:
            # 设置模拟的 sys.argv
            sys.argv = argv
            
            # 执行模拟的 setup.py 内容
            exec_globals = {
                '__file__': os.path.abspath('setup.py'),
                '__name__': '__main__',
                '__builtins__': __builtins__,
            }
            exec(setup_py_content, exec_globals)
            
        finally:
            # 恢复原始 sys.argv
            sys.argv = original_argv
        
        print("   " + "-" * 45)

def explain_your_specific_case():
    """解释你看到的具体情况"""
    
    print(f"\n🎯 你的具体情况分析:")
    print("-" * 35)
    
    print("""
你看到的：
sys.argv = ['C:\\...\\pip\\_vendor\\pyproject_hooks\\_in_process\\_in_process.py', 'egg_info']

这意味着：
1️⃣  pip 正在执行元数据收集阶段
2️⃣  setuptools.build_meta 设置了 sys.argv[1] = 'egg_info'
3️⃣  你的 setup.py 通过 exec(code, locals()) 执行
4️⃣  setup() 函数看到 sys.argv[1] = 'egg_info'
5️⃣  setuptools 执行元数据收集逻辑，不编译 C 扩展

后续阶段会看到：
- sys.argv[1] = 'build_ext' (编译扩展时)
- sys.argv[1] = 'bdist_wheel' (打包时)

关键理解：
✓ 命令通过 sys.argv 传递给 exec() 中的代码
✓ setup.py 通过 import sys 和检查 sys.argv 知道要做什么
✓ 这是标准的 distutils/setuptools 机制
✓ PEP 517 构建后端只是设置了正确的 sys.argv
    """)

def show_code_flow():
    """展示代码执行流程"""
    
    print(f"\n🔄 代码执行流程:")
    print("-" * 25)
    
    print("""
pip install -e . -v
     ↓
pip 调用 setuptools.build_meta.build_editable()
     ↓
build_meta 内部:
  1. 先调用元数据收集
     sys.argv = ['_in_process.py', 'egg_info']
     exec(setup_py_code, globals())  ← 你看到的这次调用
     
  2. 再调用实际构建  
     sys.argv = ['_in_process.py', 'build_ext', '--inplace']
     exec(setup_py_code, globals())  ← CMakeBuild 会在这次运行
     
  3. 最后打包
     sys.argv = ['_in_process.py', 'bdist_wheel']
     exec(setup_py_code, globals())
     ↓
pip 安装构建好的 wheel
    """)

def main():
    demonstrate_command_passing()
    show_mechanism_step_by_step()
    demonstrate_with_simulation()
    explain_your_specific_case()
    show_code_flow()
    
    print(f"\n" + "=" * 60)
    print("✅ 总结:")
    print("   1. 命令通过修改 sys.argv 传递给 exec() 中的代码")
    print("   2. setup.py 通过 import sys 访问 sys.argv")
    print("   3. setup() 函数根据 sys.argv[1] 决定执行什么操作")
    print("   4. PEP 517 构建后端负责设置正确的 sys.argv")
    print("   5. 这保持了与传统 python setup.py build 的兼容性")
    print("=" * 60)

if __name__ == "__main__":
    main() 